﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using AForge;
using AForge.Imaging;
using AForge.Imaging.Filters;
using PdfLib;
using Image=System.Drawing.Image;
using Common.Utilities;

namespace Workflows.Components.TextMining.EnumerationObjects
{
    /// <summary>
    /// 
    /// </summary>
    public class Phs2590Extractor
    {
        public event ExtractionError OnExtractionError;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="rule"></param>
        /// <param name="pageNumbers"></param>
        /// <returns></returns>
        public Dictionary<int, DataTable> GetDataFromFile(string filePath, ExtractionRule rule, List<int> pageNumbers)
        {
            // List<string> combinedHeaders = this.CombineAllColumnHeaders(rule.ColHeaderOptions);
            Dictionary<int, DataTable> tableByPages = new Dictionary<int, DataTable>();
            List<int> applyOrders = new List<int>();
            foreach (int applyOrder in rule.ColHeaderOptions.Keys)
            {
                applyOrders.Add(applyOrder);
            }
            applyOrders.Sort();

            foreach (int pageNum in pageNumbers)
            {
                try
                {
                    // step 2: loop through pages 
                    // step 2a: get image
                    Bitmap pageBmp = this.GetPdfPageAsBitmap(filePath, pageNum);
                    if (pageBmp == null)
                        continue;

                    // step 2: detect table layout 
                    List<Rectangle> allTableRects = this.GetRectanglesFromImage(pageBmp);
                    if (allTableRects == null || allTableRects.Count == 0)
                    {
                        throw new Exception("Unable to detect lines from page");
                    }

                    // step 3: get table data
                    DataTable dtPersonDataRaw =
                        this.GetTableDataFromPage(filePath, pageNum, pageBmp, allTableRects, rule.ColHeaderOptions);
                    if (dtPersonDataRaw != null || dtPersonDataRaw.Rows.Count > 0)
                    {
                        tableByPages.Add(pageNum, dtPersonDataRaw);
                    }

                }
                catch (Exception ex)
                {
                    if(this.OnExtractionError !=null)
                    {
                        this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex.ToString());
                    }
                }
            }
            return tableByPages;
        }

        private List<string> CombineAllColumnHeaders(Dictionary<int, List<PdfTableColumnHeaderMapping>> colHeaderOps)
        {
            List<string> colHeaders = new List<string>();
            List<int> orders = new List<int>();
            foreach (int order in colHeaderOps.Keys)
            {
                orders.Add(order);
            }
            orders.Sort();
            foreach (int order in orders)
            {
                List<PdfTableColumnHeaderMapping> colHeaderop = colHeaderOps[order];
                foreach (PdfTableColumnHeaderMapping colMap in colHeaderop)
                {
                    if ((!string.IsNullOrEmpty(colMap.FieldNameInPdf)) && (!colHeaders.Contains(colMap.FieldNameInPdf)))
                    {
                        colHeaders.Add(colMap.FieldNameInPdf);
                    }
                }
            }
            return colHeaders;
        }

        private Bitmap GetPdfPageAsBitmap(string pdfFilePath, int pageNum)
        {
            Image img = PdfImageReader.GetPageImage(pdfFilePath, pageNum);
            if(img !=null)
                return (Bitmap) img;
            else
                return null;
        }

        private List<Rectangle> GetRectanglesFromImage(Bitmap img)
        {
            try
            {
                img = this.TransformPixelFormat(img);
                img = this.EnlargeGrayScale(img);
                // this.DetectAllLines();

                //BlobCounter blobCounter = new BlobCounter(img);
                //Rectangle[] rects = blobCounter.GetObjectRectangles();
                Rectangle[] rects = this.FindRectanglesFromImage(img);
                List<Rectangle> filteredRects = new List<Rectangle>();
                Rectangle pageRect = new Rectangle();
                if (rects != null && rects.Length > 0)
                {
                    foreach (Rectangle rect in rects)
                    {
                        if ((rect.Width > 100 && rect.Height >= 20) || (rect.Width >= 20 && rect.Height > 100))
                        {
                            filteredRects.Add(rect);
                            if (rect.X == 0 && rect.Y == 0)
                            {
                                if (rect.Width > pageRect.Width && rect.Height > pageRect.Height)
                                {
                                    pageRect = rect;
                                }
                            }
                        }
                    }
                }

                List<Rectangle> rectAdded = new List<Rectangle>();
                foreach (Rectangle rect in filteredRects)
                {
                    if (!this.HasLeftCell(rect, filteredRects, pageRect))
                    {
                        Rectangle leftCell = this.CreateLeftCell(rect, pageRect);
                        rectAdded.Add(leftCell);
                    }
                    else if (!this.HasRightCell(rect, filteredRects, pageRect))
                    {
                        Rectangle rightCell = this.CreateRightCell(rect, pageRect);
                        rectAdded.Add(rightCell);
                    }
                }
                if (rectAdded.Count > 0)
                {
                    filteredRects.AddRange(rectAdded);
                }

                // remove page rect and empty rect
                List<Rectangle> rectToDraw = new List<Rectangle>();
                foreach (Rectangle rect in filteredRects)
                {
                    if (rect.Width == pageRect.Width && rect.Height == pageRect.Height)
                    {
                    } // filteredRects.Remove(rect);
                    else if (rect.Width <= 20 || rect.Height <= 20)
                    {
                    } // filteredRects.Remove(rect);
                    else
                        rectToDraw.Add(rect);
                }
               
                return rectToDraw;
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to detect rectangles from image: " + ex.Message);
            }
        }

        private DataTable GetTableDataFromPage(string pdfFilePath, int pageNum, Bitmap pageImage, List<Rectangle> allTableRectangles, Dictionary<int, List<PdfTableColumnHeaderMapping>> colHeaderMappingOps)
        {
            SizeF pageSize = PdfPropertyReader.GetPageSize(pdfFilePath, pageNum);
            float scaleX = pageSize.Width / pageImage.Width;
            float scaleY = pageSize.Height / pageImage.Height;
            List<RectangleF> allRects = this.ApplyScaleFactors(allTableRectangles, scaleX, scaleY);
            List<PdfTextToken> txtTokensByPage = PdfTextReader.ReadPageContentAsTokens(pdfFilePath, pageNum);

            List<int> applyOrders = new List<int>();
            foreach (int applyOrder in colHeaderMappingOps.Keys)
            {
                applyOrders.Add(applyOrder);
            }
            applyOrders.Sort();
            List<string> colHeaders=new List<string>();
            foreach (int order in applyOrders)
            {
                List<PdfTableColumnHeaderMapping> colHeaderMappings = colHeaderMappingOps[order];

                Dictionary<string, RectangleF> colRects =
                    this.FitColumnRectangles(pageSize, txtTokensByPage, colHeaderMappings);
                if (colRects != null && colRects.Count > 0)
                {
                    colHeaders.Clear();
                    foreach (PdfTableColumnHeaderMapping colHeaderMapping in colHeaderMappings)
                    {
                        colHeaders.Add(colHeaderMapping.FieldNameInPdf);
                    }
                    break;
                }
            }
            if(colHeaders.Count==0)
            {
                // use default column headers 
                if(allRects.Count>6)
                {
                    string[] defaultColHeaders =
                        {
                            "Name", "Degree", "SSN", "Role on Project",
                            "Date of Birth", "Cal",
                            "Acad", "Sum"
                        };
                    colHeaders.AddRange(defaultColHeaders);
                }
                else
                {
                    string[] defaultColHeaders =
                        {
                            "Name", "Degree", "SSN", "Role on Project",
                            "Date of Birth", "Annual % Effort"
                        };
                    colHeaders.AddRange(defaultColHeaders);
                }
            }
            PdfTableDataFromTextTokens tblCreator =
                new PdfTableDataFromTextTokens(txtTokensByPage, allRects, pageSize, colHeaders);
            //Dictionary<string, RectangleF> fieldRects = tblCreator.GetFieldRectangles(allRects, colHeaders);
            DataTable dtRecord = tblCreator.TableContent;
            return dtRecord;
        }

        private Bitmap TransformPixelFormat(Bitmap img)
        {
            if (img.PixelFormat != PixelFormat.Format8bppIndexed)
            {
                GrayscaleBT709 filter = new GrayscaleBT709();
                img = filter.Apply(img);
            }
            return img;
        }

        private Bitmap EnlargeGrayScale(Bitmap img)
        {
            LevelsLinear filter = new LevelsLinear();
            IntRange inRange = new IntRange(235, 255);
            IntRange outRange = new IntRange(0, 255);
            filter.InGray = inRange;
            filter.OutGray = outRange;
            img = filter.Apply(img);
            return img;
        }

        private Rectangle[] FindRectanglesFromImage(Bitmap img)
        {
            BlobCounter blobCounter = new BlobCounter(img);
            Rectangle[] rects = blobCounter.GetObjectRectangles();
            return rects;
        }

        private bool HasLeftCell(Rectangle currentRect, List<Rectangle> allRects, Rectangle pageRect)
        {
            bool hasLeftCell = false;
            foreach (Rectangle rect in allRects)
            {
                if (rect.Width < pageRect.Width && rect.Height < pageRect.Height && rect.X > 0 && rect.Y > 0)
                {
                    if (rect.Y + rect.Height > currentRect.Y && rect.Y < currentRect.Y + currentRect.Height)
                    {
                        if (rect.X + rect.Width < currentRect.X + 20)
                        {
                            hasLeftCell = true;
                            break;
                        }
                    }
                }
            }
            return hasLeftCell;
        }

        private Rectangle CreateLeftCell(Rectangle currentRect, Rectangle pageRect)
        {
            Rectangle leftCell = new Rectangle(pageRect.X, currentRect.Y, currentRect.X - pageRect.X, currentRect.Height);
            return leftCell;
        }

        private bool HasRightCell(Rectangle currentRect, List<Rectangle> allRects, Rectangle pageRect)
        {
            bool hasRightCell = false;
            foreach (Rectangle rect in allRects)
            {
                if (rect.Width < pageRect.Width && rect.Height < pageRect.Height && rect.X > 0 && rect.Y > 0)
                {
                    if (rect.Y + rect.Height > currentRect.Y && rect.Y < currentRect.Y + currentRect.Height)
                    {
                        if (rect.X + 20 > currentRect.X + currentRect.Width)
                        {
                            hasRightCell = true;
                            break;
                        }
                    }
                }
            }
            return hasRightCell;
        }

        private Rectangle CreateRightCell(Rectangle currentRect, Rectangle pageRect)
        {
            Rectangle rightCell = new Rectangle(currentRect.X + currentRect.Width, currentRect.Y, pageRect.X + pageRect.Width - currentRect.X - currentRect.Width, currentRect.Height);
            return rightCell;
        }

        private List<RectangleF> ApplyScaleFactors(List<Rectangle> rects, float scaleX, float scaleY)
        {
            List<RectangleF> adjustedRects = new List<RectangleF>();
            foreach (RectangleF rect in rects)
            {
                RectangleF newRect = new RectangleF(
                    rect.X * scaleX,
                    rect.Y * scaleY,
                    rect.Width * scaleX,
                    rect.Height * scaleY);
                adjustedRects.Add(newRect);
            }
            return adjustedRects;
        }

        private Dictionary<string, RectangleF> FitColumnRectangles(SizeF pageSize, List<PdfTextToken> allTextTokens, List<PdfTableColumnHeaderMapping> colHeaderOp)
        {
            colHeaderOp.Sort();
            Dictionary<string, PointF> colHeaderStartPositions = new Dictionary<string, PointF>();
            List<string> colHeaderNotFound = new List<string>();
            foreach (PdfTableColumnHeaderMapping colHeaderMapping in colHeaderOp)
            {
                colHeaderNotFound.Add(colHeaderMapping.FieldNameInPdf);
            }
            float yFrom = pageSize.Height;
            float yTo = 0;
            allTextTokens.Sort();
            for (int i = 0; i < allTextTokens.Count; i++)
            {
                //if (colHeaderNotFound.Count == 0)
                //    break;

                foreach (PdfTableColumnHeaderMapping colHeaderMapping in colHeaderOp)
                {
                    string[] colHeaderWords = colHeaderMapping.FieldNameInPdf.Split(new char[] { ' ' });
                    string colHeaderFirstWord = colHeaderWords[0];
                    if (!string.IsNullOrEmpty(allTextTokens[i].Text) && !string.IsNullOrEmpty(colHeaderFirstWord) && colHeaderFirstWord.Length > 0)
                    {
                        if(this.WordMatchAllowingOneCharacterMismatch(allTextTokens[i].Text, colHeaderFirstWord))
                        {
                            PointF pos = new PointF(allTextTokens[i].TextRect.X, allTextTokens[i].TextRect.Y);
                            if (colHeaderStartPositions.ContainsKey(colHeaderMapping.FieldNameInPdf))
                            {
                                // colHeaderStartPositions[colName] = pos;
                            }
                            else
                            {
                                colHeaderStartPositions.Add(colHeaderMapping.FieldNameInPdf, pos);
                                colHeaderNotFound.Remove(colHeaderMapping.FieldNameInPdf);
                                if (yFrom > pos.Y)
                                {
                                    yFrom = pos.Y;
                                }
                                if (yTo < pos.Y)
                                {
                                    yTo = pos.Y;
                                }
                            }

                            break;
                        }
                    }
                }
            }

            if (colHeaderNotFound.Count == 0)
            {
                Dictionary<string, RectangleF> rectanglesForTableContents = new Dictionary<string, RectangleF>();
                float lastX = pageSize.Width;
                for (int i = colHeaderOp.Count - 1; i >= 0; i--)
                {
                    string colName = colHeaderOp[i].FieldNameInPdf;
                    PointF colPos = colHeaderStartPositions[colName];
                    RectangleF colRect = new RectangleF(colPos.X, yTo, lastX - colPos.X, pageSize.Height - yTo);
                    rectanglesForTableContents.Add(colName, colRect);
                    lastX = colPos.X;
                }
                return rectanglesForTableContents;
            }
            else
                return null;
        }


        private bool WordMatchAllowingOneCharacterMismatch(string subj, string pattern)
        {
            if (string.IsNullOrEmpty(subj) || string.IsNullOrEmpty(pattern))
                return false;

            if (subj.ToLower() == pattern.ToLower())
            {
                return true;
            }
            else if (StringUtil.LongestCommonSubString(subj, pattern) >= pattern.Length - 1)
            {
                return true;
            }
            else if (Math.Abs(subj.Length - pattern.Length) <= 1 && StringUtil.LongestCommonSubsequence(subj, pattern) >= pattern.Length - 1)
            {
                return true;
            }
            else
                return false;
        }
    }
}
